﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using Tesla.Core;
using Tesla.Graphics.Implementation;
using Tesla.Math;

namespace Tesla.Graphics {
    /// <summary>
    /// SwapChain represents the back/front buffers that the scene is rendered to. The back buffer
    /// accumulates the rendered primitives and is presented to the window that is being rendered to (e.g. swapped with
    /// the front buffer).
    /// </summary>
    public class SwapChain : GraphicsResource {
        private SwapChainImplementation _impl;

        /// <summary>
        /// Gets the API-specific implementation of this resource.
        /// </summary>
        public override GraphicsResourceImplementation Implementation {
            get {
                return _impl;
            }
        }
        
        /// <summary>
        /// Gets the presentation parameters the swap chain is set to.
        /// </summary>
        public PresentationParameters PresentationParameters {
            get {
                return _impl.PresentationParameters;
            }
        }

        /// <summary>
        /// Gets if the swap chain is in full screen mode or not. By default, swap chains
        /// are not in full screen.
        /// </summary>
        public bool IsFullScreen {
            get {
                return _impl.IsFullScreen;
            }
        }

        /// <summary>
        /// Gets the window handle the swap chain is bound to.
        /// </summary>
        public IntPtr WindowHandle {
            get {
                return _impl.WindowHandle;
            }
        }

        /// <summary>
        /// Gets if the current display mode is in wide screen or not.
        /// </summary>
        public bool IsWideScreen {
            get {
                return _impl.IsWideScreen;
            }
        }

        /// <summary>
        /// Gets the current display mode of the swap chain.
        /// </summary>
        public DisplayMode CurrentDisplayMode {
            get {
                return _impl.CurrentDisplayMode;
            }
        }

        /// <summary>
        /// Gets the handle to the monitor that contains the majority of the output.
        /// </summary>
        public IntPtr MonitorHandle {
            get {
                return _impl.MonitorHandle;
            }
        }

        /// <summary>
        /// Creates a new instance of <see cref="SwapChain"/>.
        /// </summary>
        /// <param name="windowHandle">The handle to the window the swap chain is to bounded to. This is the window that the swap chain
        /// presents to.</param>
        /// <param name="presentParams">The presentation parameters defining how the swap chain should be setup..</param>
        /// <exception cref="System.ArgumentException">Thrown if the presentation parameters are invalid, e.g. bad format or multi sample values.</exception>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if the render system is not set or if the implementation failed to be created.</exception>
        public SwapChain(IntPtr windowHandle, PresentationParameters presentParams) {
            IRenderSystemProvider renderSystem = Engine.Services.GetService<IRenderSystemProvider>();
            if(renderSystem == null) {
                Dispose();
                throw new TeslaException("Render system provider not set, cannot create graphics resource implementation.");
            }

            base.RenderSystem = renderSystem;

            //Check format compatibility
            bool goodFormat = renderSystem.DefaultAdapter.QueryBackBufferFormat(presentParams.BackBufferFormat, presentParams.DepthStencilFormat, presentParams.MultiSampleCount);
            int sampleQual = renderSystem.DefaultAdapter.QueryMultiSampleQualityLevels(presentParams.BackBufferFormat, presentParams.MultiSampleQuality);
            int levels = presentParams.MultiSampleQuality;
            if(goodFormat && (levels >= 0 && levels <= sampleQual)) {
                try {
                    _impl = renderSystem.CreateSwapChainImplementation(windowHandle, presentParams);
                } catch(Exception e) {
                    Dispose();
                    throw new TeslaException("Error creating underlying implementation, see inner exception for details.", e);
                }
            } else {
                Dispose();
                throw new ArgumentException("Invalid presentation parameters.");
            }
        }

        /// <summary>
        /// Creates a new instance of <see cref="SwapChain"/>.
        /// </summary>
        /// <param name="renderSystem">Render system used to create the underlying implementation.</param>
        /// <param name="windowHandle">The handle to the window the swap chain is to bounded to. This is the window that the swap chain
        /// presents to.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if the render system is null.</exception>
        /// <param name="presentParams">The presentation parameters defining how the swap chain should be setup..</param>
        /// <exception cref="System.ArgumentException">Thrown if the presentation parameters are invalid, e.g. bad format or multi sample values.</exception>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if the implementation failed to be created.</exception>
        public SwapChain(IRenderSystemProvider renderSystem, IntPtr windowHandle, PresentationParameters presentParams) {
            if(renderSystem == null) {
                Dispose();
                throw new ArgumentNullException("renderSystem", "Render system cannot be null.");
            }

            base.RenderSystem = renderSystem;

            //Check format compatibility
            bool goodFormat = renderSystem.DefaultAdapter.QueryBackBufferFormat(presentParams.BackBufferFormat, presentParams.DepthStencilFormat, presentParams.MultiSampleCount);
            int sampleQual = renderSystem.DefaultAdapter.QueryMultiSampleQualityLevels(presentParams.BackBufferFormat, presentParams.MultiSampleQuality);
            int levels = presentParams.MultiSampleQuality;
            if(goodFormat && (levels >= 0 && levels <= sampleQual)) {
                try {
                    _impl = renderSystem.CreateSwapChainImplementation(windowHandle, presentParams);
                } catch(Exception e) {
                    Dispose();
                    throw new TeslaException("Error creating underlying implementation, see inner exception for details.", e);
                }
            } else {
                Dispose();
                throw new ArgumentException("Invalid presentation parameters.");
            }
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="SwapChain"/> is reclaimed by garbage collection.
        /// </summary>
        ~SwapChain() {
            Dispose(false);
        }

        /// <summary>
        /// Clears everything (Target, Depth, and Stencil) using the default values for depth (1.0f) and stencil (0.0f).
        /// </summary>
        /// <remarks>
        /// The use of a swapchain differs slightly from using render targets. When a swapchain's
        /// targets are set to the device, other render targets (off screen) can be set. To make
        /// the backbuffer active again, simply set null to the renderer.
        /// </remarks>
        /// <param name="color">Color value to clear the target to</param>
        public void Clear(Color color) {
            _impl.Clear(ClearOptions.Target | ClearOptions.Depth | ClearOptions.Stencil, color, 1.0f, 0);
        }

        /// <summary>
        /// Clears the back buffer.
        /// </summary>
        /// <remarks>
        /// The use of a swapchain differs slightly from using render targets. When a swapchain's
        /// targets are set to the device, other render targets (off screen) can be set. To make
        /// the backbuffer active again, simply set null to the renderer.
        /// </remarks>
        /// <param name="options">Specifies which buffer to clear.</param>
        /// <param name="color">Color value to clear the target to</param>
        /// <param name="depth">Depth value to clear the depth buffer to</param>
        /// <param name="stencil">Stencil value to clear the stencil buffer to</param>
        public void Clear(ClearOptions options, Color color, float depth, int stencil) {
            _impl.Clear(options, color, depth, stencil);
        }

        /// <summary>
        /// Gets the backbuffer as an array of data.
        /// </summary>
        /// <typeparam name="T">Specified data type</typeparam>
        /// <param name="data">The array to store the data in</param>
        public void GetBackBufferData<T>(T[] data) where T : struct {
            if(data == null || data.Length == 0) {
                throw new ArgumentNullException("data", "Data cannot be null");
            }
            _impl.GetBackBufferData<T>(null, data, 0, data.Length);
        }

        /// <summary>
        /// Gets the backbuffer as an array of data.
        /// </summary>
        /// <typeparam name="T">Specified data type</typeparam>
        /// <param name="data">The array to store the data in</param>
        /// <param name="startIndex">The starting index in the array</param>
        /// <param name="elementCount">The number of elements to copy</param>
        public void GetBackBufferData<T>(T[] data, int startIndex, int elementCount) where T : struct {
            if(data == null || data.Length == 0) {
                throw new ArgumentNullException("data", "Data cannot be null");
            }
            _impl.GetBackBufferData<T>(null, data, startIndex, elementCount);
        }

        /// <summary>
        /// Gets the backbuffer as an array of data.
        /// </summary>
        /// <typeparam name="T">Specified data type</typeparam>
        /// <param name="rect">Rectangle defining the sub-image, if null the whole image is returned</param>
        /// <param name="data">The array to store the data in</param>
        /// <param name="startIndex">The starting index in the array</param>
        /// <param name="elementCount">The number of elements to copy</param>
        public void GetBackBufferData<T>(Rectangle? rect, T[] data, int startIndex, int elementCount) where T : struct {
            if(data == null || data.Length == 0) {
                throw new ArgumentNullException("data", "Data cannot be null");
            }

            _impl.GetBackBufferData<T>(rect, data, startIndex, elementCount);
        }

        /// <summary>
        /// Presents the contents of the back buffer to the screen, and flips the front/back buffers.
        /// </summary>
        public void Present() {
            _impl.Present();
        }

        /// <summary>
        /// Toggles the swap chain to full screen mode.
        /// </summary>
        public void ToggleFullScreen() {
            _impl.ToggleFullScreen();
        }

        /// <summary>
        /// Resizes the backbuffer.
        /// </summary>
        /// <param name="width">Width of the back buffer</param>
        /// <param name="height">Height of the back buffer</param>
        public void Resize(int width, int height) {
            _impl.Resize(width, height);
        }

        /// <summary>
        /// Resets the swapchain.
        /// </summary>
        /// <param name="windowHandle">The new window handle to bind to</param>
        /// <param name="presentParams">The new presentation parameters to use</param>
        public void Reset(IntPtr windowHandle, PresentationParameters presentParams) {
            _impl.Reset(windowHandle, presentParams);
        }
    }
}
